package com.freewater.parking.application;

import com.freewater.parking.domain.common.DomainEvent;
import com.freewater.parking.domain.common.EventQueue;
import com.freewater.parking.domain.common.PlateNumber;
import com.freewater.parking.domain.parking.*;
import com.freewater.parking.infrastructure.event.DomainEventDispatcher;
import com.freewater.parking.infrastructure.event.SimpleEventQueue;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Optional;

@Slf4j
@Service
@Transactional
@AllArgsConstructor
public class ParkingAppServiceImpl implements ParkingAppService {

    private ParkingRepository parkings;
    private ParkingService parkingService;
    private DomainEventDispatcher eventDispatcher;

    @Override
    public boolean checkIn(CheckInCommand checkInCommand) {
        PlateNumber plateNumber = checkInCommand.getPlateNumber();
        Parking parking = this.findOrNewParking(plateNumber);
        EventQueue queue = new SimpleEventQueue();
        LocalDateTime checkInTime = checkInCommand.getCheckInTime();
        boolean checkInResult = this.parkingService.checkIn(queue, parking, checkInTime);
        if (checkInResult) {
            this.parkings.save(parking);
        }
        dispatchEvents(queue);
        return checkInResult;
    }

    @Override
    public boolean checkOut(CheckOutCommand checkOutCommand) {
        PlateNumber plateNumber = checkOutCommand.getPlateNumber();
        Parking parking = this.findOrNewParking(plateNumber);
        EventQueue queue = new SimpleEventQueue();
        LocalDateTime checkOutTime = checkOutCommand.getCheckOutTime();
        boolean checkOutResult = parkingService.checkOut(queue, parking, checkOutTime);
        if (checkOutResult) {
            this.parkings.save(parking);
        }
        dispatchEvents(queue);
        return checkOutResult;
    }

    @Override
    public int calculateFee(CalculateFeeCommand calculateFeeCommand) {
        PlateNumber plateNumber = calculateFeeCommand.getPlateNumber();
        Parking parking = this.findOrNewParking(plateNumber);
        EventQueue queue = new SimpleEventQueue();
        LocalDateTime time = calculateFeeCommand.getTime();
        return parkingService.calcParkingFee(queue, parking, time);
    }

    @Override
    public void notifyPay(NotifyPayCommand notifyPayCommand) {
        PlateNumber plateNumber = notifyPayCommand.getPlateNumber();
        Parking parking = this.findOrNewParking(plateNumber);
        EventQueue queue = new SimpleEventQueue();
        int payMoney = notifyPayCommand.getAmount();
        LocalDateTime payTime = notifyPayCommand.getPayTime();
        parkingService.payParkingFee(queue, parking, payMoney, payTime);
        this.parkings.save(parking);
        dispatchEvents(queue);
    }

    private Parking findOrNewParking(PlateNumber plateNumber) {
        Optional<Parking> parkingOpt = this.parkings.findByPlateNumber(plateNumber);
        return parkingOpt.orElse(new Parking(plateNumber));
    }

    private void dispatchEvents(EventQueue queue) {
        for (DomainEvent domainEvent : queue.queue()) {
            eventDispatcher.dispatch(domainEvent);
        }
        queue.clear();
    }
}
